package com.yys.gulimall.ware.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.yys.common.constant.OrderConstant;
import com.yys.common.to.es.SkuHasStockTo;
import com.yys.common.to.mq.OrderTo;
import com.yys.common.to.mq.StockDetailTo;
import com.yys.common.to.mq.StockLockTo;
import com.yys.common.utils.R;
import com.yys.gulimall.ware.entity.WareOrderTaskDetailEntity;
import com.yys.gulimall.ware.entity.WareOrderTaskEntity;
import com.yys.gulimall.ware.exception.NoStockException;
import com.yys.gulimall.ware.feign.OrderFeignService;
import com.yys.gulimall.ware.feign.ProductFeignService;
import com.yys.gulimall.ware.service.WareOrderTaskDetailService;
import com.yys.gulimall.ware.service.WareOrderTaskService;
import com.yys.gulimall.ware.vo.OrderItemVo;
import com.yys.gulimall.ware.vo.OrderVo;
import com.yys.gulimall.ware.vo.WareSkuLockVo;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yys.common.utils.PageUtils;
import com.yys.common.utils.Query;

import com.yys.gulimall.ware.dao.WareSkuDao;
import com.yys.gulimall.ware.entity.WareSkuEntity;
import com.yys.gulimall.ware.service.WareSkuService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import static com.yys.gulimall.ware.constant.WareConstant.*;


@Service("wareSkuService")
@Slf4j
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private WareOrderTaskService wareOrderTaskService;

    @Autowired
    private WareOrderTaskDetailService wareOrderTaskDetailService;

    @Autowired
    private ProductFeignService productFeignService;

    @Autowired
    private OrderFeignService orderFeignService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        QueryWrapper<WareSkuEntity> wrapper = new QueryWrapper<>();

        String skuId = (String) params.get("skuId");
        if(!StringUtils.isEmpty(skuId)) {
            wrapper.eq("sku_id",skuId);
        }

        String wareId = (String) params.get("wareId");
        if(!StringUtils.isEmpty(wareId)) {
            wrapper.eq("ware_id",wareId);
        }

        IPage<WareSkuEntity> page = this.page(
                new Query<WareSkuEntity>().getPage(params),
                wrapper
        );

        return new PageUtils(page);
    }

    @Override
    public void addStock(Long skuId, Long wareId, Integer skuNum) {
        // 判断是添加还是更新
        List<WareSkuEntity> list = this.list(new QueryWrapper<WareSkuEntity>().eq("sku_id", skuId).eq("ware_id", wareId));
        if(ObjectUtils.isEmpty(list)) {
            WareSkuEntity wareSkuEntity = new WareSkuEntity();
            wareSkuEntity.setWareId(wareId);
            wareSkuEntity.setSkuId(skuId);
            wareSkuEntity.setStock(skuNum);
            wareSkuEntity.setStockLocked(0);
            //TODO 远程调用product获取skuName,1.直接try 不让他回滚 -- 2. 还可以 ...
            try {
                R info = productFeignService.info(skuId);
                if(info.getCode() == 0) {
                    Map<String,Object> skuInfo = (Map<String, Object>) info.get("skuInfo");
                    wareSkuEntity.setSkuName((String) skuInfo.get("skuName"));
                }
            } catch (Exception ignored) {}
            this.save(wareSkuEntity);
            return ;
        }
        this.baseMapper.addStock(skuId,wareId,skuNum);
    }

    @Override
    public List<SkuHasStockTo> getSkusHasStock(List<Long> skuIds) {

        return skuIds.stream().map(skuId -> {
            SkuHasStockTo skuHasStockVo = new SkuHasStockTo();
            Long skuStock = this.baseMapper.getSkuStock(skuId);
            skuHasStockVo.setSkuId(skuId);

            if(skuStock == null) {
                skuHasStockVo.setHasStock(false);
            } else {
                skuHasStockVo.setHasStock(skuStock > 0);
            }

            return skuHasStockVo;
        }).collect(Collectors.toList());
    }


    /**
     * @param wareSkuLockVo vo
     * @return 返回是否有库存
     * 监听队列库存自动解锁场景
     * 1. 下单成功,订单过期没有支付被系统自动取消,被用户手动取消
     * 2. 下订单成功,锁库存成功,后面业务出错回滚,需要解锁库存
     */
    @Override
    @Transactional
    public Boolean orderLockStock(WareSkuLockVo wareSkuLockVo) {
        /*
            保存库存工作单详情,用于追溯日志
         */
        WareOrderTaskEntity wareOrderTaskEntity = new WareOrderTaskEntity();
        wareOrderTaskEntity.setOrderSn(wareSkuLockVo.getOrderSn());
        wareOrderTaskService.save(wareOrderTaskEntity);

        // 找到商品在哪个仓库有库存
        List<OrderItemVo> locks = wareSkuLockVo.getLocks();
        List<SkuWareHasStock> skuWareHasStocks = locks.stream().map(item -> {
            Long skuId = item.getSkuId();
            SkuWareHasStock skuWareHasStock = new SkuWareHasStock();
            skuWareHasStock.setSkuId(skuId);
            skuWareHasStock.setNum(item.getCount());
            // 查询商品在哪有库存
            List<Long> wareIds = this.baseMapper.listWareIdHasSkuStock(skuId);
            skuWareHasStock.setWareId(wareIds);
            return skuWareHasStock;
        }).collect(Collectors.toList());

        boolean allLock = true;
        // 锁定库存
        for (SkuWareHasStock skuWareHasStock : skuWareHasStocks) {
            boolean skuStocked = false;
            Long skuId = skuWareHasStock.getSkuId();
            List<Long> wareIds = skuWareHasStock.getWareId();
            if(ObjectUtils.isNotEmpty(wareIds)) {
                for (Long wareId : wareIds) {
                    // 1表示成功 0 表示失败
                    // 若锁定成功,当前商品锁定了多少都保存在工作详情单中,同时向mq保存数据
                    // 若锁定失败,前面保存的工作单信息就回滚了,即使nq发送出去了也没问题
                    Long count = this.baseMapper.lockSkuStock(skuId, wareId, skuWareHasStock.getNum());
                    if(count == 1) {
                        skuStocked = true;
                        // TODO 告诉mq 锁定成功   同时保存此条数据
                        WareOrderTaskDetailEntity wareOrderTaskDetailEntity = new WareOrderTaskDetailEntity();
                        wareOrderTaskDetailEntity.setSkuId(skuId);
                        wareOrderTaskDetailEntity.setTaskId(wareOrderTaskEntity.getId());
                        wareOrderTaskDetailEntity.setWareId(wareId);
                        wareOrderTaskDetailEntity.setLockStatus(WARE_TASK_DETAIL_LOCK_STATUS_LOCK);
                        wareOrderTaskDetailEntity.setSkuNum(skuWareHasStock.getNum());
                        wareOrderTaskDetailService.save(wareOrderTaskDetailEntity);
                        // 发送消息到delayQueue中,过期会自动转到releaseQueue中
                        StockLockTo stockLockTo = new StockLockTo();
                        stockLockTo.setId(wareOrderTaskEntity.getId());
                        StockDetailTo stockDetailTo = new StockDetailTo();
                        BeanUtils.copyProperties(wareOrderTaskDetailEntity,stockDetailTo);
                        // 只发id不行,回滚会找不到数据
                        stockLockTo.setStockDetailTo(stockDetailTo);
                        rabbitTemplate.convertAndSend(STOCK_EVENT_EXCHANGE,STOCK_LOCKED_ROUTING_KEY,stockLockTo);
                        break;
                    }
                    // 锁失败重试下一个
                }
                if(!skuStocked) {
                    // 所有商品都没锁住
                    throw new NoStockException(skuId);
                }
            } else {
                throw new NoStockException(skuId);
            }
        }
        // 这里肯定都是全部锁定成功的
        return allLock;
    }

    @Override
    public void unlockStock(StockLockTo stockLockTo) {
        // 锁库存成功会有库存工作单,可以按照库存工作单来回滚
        StockDetailTo stockDetailTo = stockLockTo.getStockDetailTo();
        WareOrderTaskDetailEntity detailEntity = wareOrderTaskDetailService.getById(stockDetailTo.getId());
        if(ObjectUtils.isNotNull(detailEntity)) {
            // 有 说明库存锁定成功
            // 是否解锁还需要看订单情况
            // 1. 没有订单 -> 必须解锁
            // 2. 有订单 -> 判断订单状态
            //      1. 已取消: 解锁库存
            //      2. 没取消: 不能解锁
            Long lockToId = stockLockTo.getId();
            WareOrderTaskEntity wareOrderTaskEntity = wareOrderTaskService.getById(lockToId);
            String orderSn = wareOrderTaskEntity.getOrderSn();
            R r = orderFeignService.getOrderStatus(orderSn);
            if(r.getCode() == 0) {
                OrderVo orderVo = r.getData(new TypeReference<OrderVo>() {});
                // 订单不存在或取消 都要解锁库存
                if(ObjectUtils.isNull(orderVo) || orderVo.getStatus() == OrderConstant.ORDER_STATUS_CLOSE) {
                    if(detailEntity.getLockStatus() == WARE_TASK_DETAIL_LOCK_STATUS_LOCK) {
                        //ack 消息
                        unLockStock(stockDetailTo);
                    }
                }
            }
            else {
                throw new RuntimeException("远程服务调用失败...");
            }
        }
    }

    /**
     * @param orderTo 订单entity
     * 防止订单卡顿导致库存不能解锁
     */
    @Override
    @Transactional
    public void unlockStock(OrderTo orderTo) {
        String orderSn = orderTo.getOrderSn();
        // 查库存解锁状态防止重复解锁
        WareOrderTaskEntity wareOrderTaskEntity = wareOrderTaskService.getOrderTaskByOrderSn(orderSn);
        // 按照工作单找到没有解锁的库存进行解锁
        List<WareOrderTaskDetailEntity> taskDetailEntityList = wareOrderTaskDetailService.list(
                new QueryWrapper<WareOrderTaskDetailEntity>()
                        .eq("task_id", wareOrderTaskEntity.getId())
                        .eq("lock_status", WARE_TASK_DETAIL_LOCK_STATUS_LOCK)
        );
        for (WareOrderTaskDetailEntity detailEntity : taskDetailEntityList) {
            StockDetailTo stockDetailTo = new StockDetailTo();
            BeanUtils.copyProperties(detailEntity,stockDetailTo);
            unLockStock(stockDetailTo);
        }
    }

    private void unLockStock(StockDetailTo stockDetailTo) {
        this.baseMapper.unlockStock(stockDetailTo);
        // 更新库存工作单状态
        WareOrderTaskDetailEntity wareOrderTaskDetailEntity = new WareOrderTaskDetailEntity();
        wareOrderTaskDetailEntity.setId(stockDetailTo.getId());
        wareOrderTaskDetailEntity.setLockStatus(WARE_TASK_DETAIL_LOCK_STATUS_UNLOCK);
        wareOrderTaskDetailService.updateById(wareOrderTaskDetailEntity);
    }

    @Data
    class SkuWareHasStock {
        private Long skuId;
        private Integer num;
        private List<Long> wareId;
    }


}